# Copyright 2020, Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import absolute_import, division, print_function
__metaclass__ = type

from ansible.module_utils.basic import AnsibleModule
try:
    from ansible.module_utils.ca_common import exit_module, \
                                               generate_cmd, \
                                               is_containerized, \
                                               exec_command
except ImportError:
    from module_utils.ca_common import exit_module, \
                                       generate_cmd, \
                                       is_containerized, \
                                       exec_command
import datetime
import json


ANSIBLE_METADATA = {
    'metadata_version': '1.1',
    'status': ['preview'],
    'supported_by': 'community'
}

DOCUMENTATION = '''
---
module: ceph_crush_rule
short_description: Manage Ceph Crush Replicated/Erasure Rule
version_added: "2.8"
description:
    - Manage Ceph Crush rule(s) creation, deletion and updates.
options:
    name:
        description:
            - name of the Ceph Crush rule. If state is 'info' - empty string
              can be provided as a value to get all crush rules
        required: true
    cluster:
        description:
            - The ceph cluster name.
        required: false
        default: ceph
    state:
        description:
            If 'present' is used, the module creates a rule if it doesn't
            exist or update it if it already exists.
            If 'absent' is used, the module will simply delete the rule.
        required: false
        choices: ['present', 'absent']
        default: present
    rule_type:
        description:
            - The ceph CRUSH rule type.
        required: false
        choices: ['replicated', 'erasure']
        required: false
    bucket_root:
        description:
            - The ceph bucket root for replicated rule.
        required: false
    bucket_type:
        description:
            - The ceph bucket type for replicated rule.
        required: false
        choices: ['osd', 'host', 'chassis', 'rack', 'row', 'pdu', 'pod',
                 'room', 'datacenter', 'zone', 'region', 'root']
    device_class:
        description:
            - The ceph device class for replicated rule.
        required: false
    profile:
        description:
            - The ceph erasure profile for erasure rule.
        required: false
author:
    - Dimitri Savineau <dsavinea@redhat.com>
'''

EXAMPLES = '''
- name: create a Ceph Crush replicated rule
  ceph_crush_rule:
    name: foo
    bucket_root: default
    bucket_type: host
    device_class: ssd
    rule_type: replicated

- name: create a Ceph Crush erasure rule
  ceph_crush_rule:
    name: foo
    profile: bar
    rule_type: erasure

- name: get a Ceph Crush rule information
  ceph_crush_rule:
    name: foo
    state: info

- name: delete a Ceph Crush rule
  ceph_crush_rule:
    name: foo
    state: absent
'''

RETURN = '''#  '''


def create_rule(module, container_image=None):
    '''
    Create a new crush replicated/erasure rule
    '''

    cluster = module.params.get('cluster')
    name = module.params.get('name')
    rule_type = module.params.get('rule_type')
    bucket_root = module.params.get('bucket_root')
    bucket_type = module.params.get('bucket_type')
    device_class = module.params.get('device_class')
    profile = module.params.get('profile')

    if rule_type == 'replicated':
        args = ['create-replicated', name, bucket_root, bucket_type]
        if device_class:
            args.append(device_class)
    else:
        args = ['create-erasure', name]
        if profile:
            args.append(profile)

    cmd = generate_cmd(sub_cmd=['osd', 'crush', 'rule'],
                       args=args,
                       cluster=cluster,
                       container_image=container_image)

    return cmd


def get_rule(module, container_image=None):
    '''
    Get existing crush rule
    '''

    cluster = module.params.get('cluster')
    name = module.params.get('name')

    args = ['dump', name, '--format=json']

    cmd = generate_cmd(sub_cmd=['osd', 'crush', 'rule'],
                       args=args,
                       cluster=cluster,
                       container_image=container_image)

    return cmd


def remove_rule(module, container_image=None):
    '''
    Remove a crush rule
    '''

    cluster = module.params.get('cluster')
    name = module.params.get('name')

    args = ['rm', name]

    cmd = generate_cmd(sub_cmd=['osd', 'crush', 'rule'],
                       args=args,
                       cluster=cluster,
                       container_image=container_image)

    return cmd


def main():
    module = AnsibleModule(
        argument_spec=dict(
            name=dict(type='str', required=False),
            cluster=dict(type='str', required=False, default='ceph'),
            state=dict(type='str', required=False, choices=['present', 'absent'], default='present'),  # noqa: E501
            rule_type=dict(type='str', required=False, choices=['replicated', 'erasure']),  # noqa: E501
            bucket_root=dict(type='str', required=False),
            bucket_type=dict(type='str', required=False, choices=['osd', 'host', 'chassis', 'rack', 'row', 'pdu', 'pod',  # noqa: E501
                                                                  'room', 'datacenter', 'zone', 'region', 'root']),  # noqa: E501
            device_class=dict(type='str', required=False),
            profile=dict(type='str', required=False)
        ),
        supports_check_mode=True,
        required_if=[
            ('state', 'present', ['rule_type']),
            ('state', 'present', ['name']),
            ('state', 'absent', ['name']),
            ('rule_type', 'replicated', ['bucket_root', 'bucket_type']),
            ('rule_type', 'erasure', ['profile'])
        ]
    )

    # Gather module parameters in variables
    name = module.params.get('name')
    state = module.params.get('state')
    rule_type = module.params.get('rule_type')

    if module.check_mode:
        module.exit_json(
            changed=False,
            stdout='',
            stderr='',
            rc=0,
            start='',
            end='',
            delta='',
        )

    startd = datetime.datetime.now()
    changed = False

    # will return either the image name or None
    container_image = is_containerized()

    rc, cmd, out, err = exec_command(module, get_rule(module, container_image=container_image))  # noqa: E501
    if state == "present":
        if rc != 0:
            rc, cmd, out, err = exec_command(module, create_rule(module, container_image=container_image))  # noqa: E501
            changed = True
        else:
            rule = json.loads(out)
            if (rule['type'] == 1 and rule_type == 'erasure') or (rule['type'] == 3 and rule_type == 'replicated'):  # noqa: E501
                module.fail_json(msg="Can not convert crush rule {} to {}".format(name, rule_type), changed=False, rc=1)  # noqa: E501
    elif state == "absent":
        if rc == 0:
            rc, cmd, out, err = exec_command(module, remove_rule(module, container_image=container_image))  # noqa: E501
            changed = True
        else:
            rc = 0
            out = "Crush Rule {} doesn't exist".format(name)
    else:
        pass

    exit_module(module=module, out=out, rc=rc, cmd=cmd, err=err, startd=startd, changed=changed)  # noqa: E501


if __name__ == '__main__':
    main()
